//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  EtwTraceProvider.cs
//    
// Abstract:
//
//  This module implements the EtwTraceProvider class.
//  
//--

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Microsoft.OfficeCommunicationsServer.Applications.Common
{
    internal unsafe delegate uint EtwTraceCallback([In] uint requestCode, [In] System.IntPtr requestContext, [In] System.IntPtr bufferSize, [In] byte* buffer);

    /// <summary>
    /// ETW provider for diagnostic logs.
    /// </summary>
    internal class EtwTraceProvider
    {
        private static Guid TraceGuid = new Guid("5C5F727D-C874-4a7b-B12F-B425AA80F11B");
        private static Guid ProviderGuid = new Guid("3A0BBD82-0BB4-4c06-BE1B-38BA692435E6");
        private const int MaxSupportedStringSize = 65486;

        private Guid _controlGuid;
        private EtwTraceCallback _etwProc;
        private ulong _registrationHandle;
        private ulong _traceHandle;

        /// <summary>
        /// ctor. Registers this provider with ETW.
        /// </summary>
        internal unsafe EtwTraceProvider()
        {
            _controlGuid = ProviderGuid;

            _etwProc = new EtwTraceCallback(EtwNotificationCallback);

            // This parameter is irrelevant because this provider never calls TraceEventInstance, but the parameter still 
            // needs to be specified on Windows XP 
            TraceGuidRegistration guidReg = new TraceGuidRegistration();
            Guid dummyGuid = new Guid("{aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaabbb}");
            guidReg.Guid = &dummyGuid;
            guidReg.RegHandle = null;

            // Register this provider
            uint status = EtwNativeMethods.RegisterTraceGuids(
                _etwProc,                   // ControlCallback function
                null,                       // Optional provider-defined context (not used)
                ref _controlGuid,           // Provider GUID
                1,                          // Number of elements in the TRACE_GUID_REGISTRATION array
                ref guidReg,                // TRACE_GUID_REGISTRATION array
                null,                       // Not supported 
                null,                       // Not supported
                out _registrationHandle);   // The provider's registration handle

            if (status != 0)
            {
                throw new Win32Exception((int)status);
            }
        }

        /// <summary>
        /// Unregisters this provider with ETW.
        /// </summary>
        internal void Close()
        {
            int errorCode = EtwNativeMethods.UnregisterTraceGuids(this._registrationHandle);
            Debug.Assert(errorCode >= 0, "errorCode >= 0");
        }

        /// <summary>
        /// Writes a trace event to ETW.
        /// </summary>
        /// <param name="guid">Activity ID associated with this trace event</param>
        /// <param name="level">Level</param>
        /// <param name="message">Message associated with this trace event</param>
        internal unsafe void WriteTrace(Guid guid, byte level, string message)
        {
            Debug.Assert(!String.IsNullOrEmpty(message), "!String.IsNullOrEmpty(message)");

            if (ShouldTrace)
            {
                int dataLength = (message.Length + 1) * 2 < MaxSupportedStringSize ? (message.Length + 1) * 2 : MaxSupportedStringSize;

                TraceEvent traceEvent = new TraceEvent();
                traceEvent.Header.Guid = TraceGuid;
                traceEvent.Header.Type = 0; // Trace
                traceEvent.Header.Level = level;
                traceEvent.Header.ClientContext = 0;
                traceEvent.Header.Flags = WnodeFlags.WnodeFlagTracedGuid | WnodeFlags.WnodeFlagUseMofPointer;
                traceEvent.Header.BufferSize = (ushort)EtwStructSizes.SizeofEventHeader + 2 * (ushort)EtwStructSizes.SizeofMofField;

                traceEvent.Mof2.Length = (uint)dataLength;

                traceEvent.Mof1.Length = 16;
                traceEvent.Mof1.Data = (IntPtr)(&guid);

                fixed (char* pdata = message)
                {
                    traceEvent.Mof2.Data = (IntPtr)pdata;
                    uint result = EtwNativeMethods.TraceEvent(_traceHandle, (char*)&traceEvent);
                    Debug.Assert(result == 0, "result == 0");
                }
            }
        }

        /// <summary>
        /// Indicates if the provider is enabled.
        /// </summary>
        internal bool ShouldTrace
        {
            get
            {
                return _traceHandle != 0;
            }
        }

        /// <summary>
        /// The provider's GUID.
        /// </summary>
        /// <remarks>
        /// This is only used by the unit tests.
        /// </remarks>
        internal static Guid Guid
        {
            get
            {
                return ProviderGuid;
            }
        }

        /// <summary>
        /// Handles enable/disable notifications from ETW.
        /// </summary>
        /// <param name="requestCode">Request code</param>
        /// <param name="context">Provider-defined context</param>
        /// <param name="bufferSize">Size of the buffer</param>
        /// <param name="buffer">Information about the ETW session</param>
        /// <returns>
        /// Returns zero.
        /// </returns>
        private unsafe uint EtwNotificationCallback(uint requestCode, System.IntPtr context, System.IntPtr bufferSize, byte* buffer)
        {
            if (null == buffer)
            {
                // The return value will be ignored
                return uint.MaxValue;
            }

            EventTraceHeader* eventBuffer = (EventTraceHeader*)buffer;

            switch (requestCode)
            {
                case RequestCodes.EnableEvents:
                    this._traceHandle = eventBuffer->HistoricalContext;
                    break;

                case RequestCodes.DisableEvents:
                    this._traceHandle = 0;
                    break;

                default:
                    this._traceHandle = 0;
                    break;
            }

            return 0;
        }
    }

    /// <summary>
    /// Request codes from ETW.
    /// </summary>
    internal static class RequestCodes
    {
        internal const int EnableEvents = 4;      // Enable Tracing
        internal const int DisableEvents = 5;     // Disable Tracing
    }

    /// <summary>
    /// Defines the values for EVENT_TRACE_HEADER.Flags.
    /// </summary>
    internal static class WnodeFlags
    {
        // Must contain WNODE_FLAG_TRACED_GUID
        internal const uint WnodeFlagTracedGuid = 0x00020000;
        //Specifies that the event data is appended to the EVENT_TRACE_HEADER structure using an array of MOF_FIELD structures
        internal const uint WnodeFlagUseMofPointer = 0x00100000;
    }

    /// <summary>
    /// Sizes of some of the structures defined below.
    /// </summary>
    internal enum EtwStructSizes
    {
        SizeofMofField = 16,
        SizeofGuid = 16,
        SizeofEventHeader = 48,
        SizeofBaseEvent = 176,
    }

    /// <summary>
    /// The EVENT_TRACE_HEADER structure contains standard event tracing information common to all events. 
    /// </summary>
    [StructLayout(LayoutKind.Explicit, Size = 48)]
    internal struct EventTraceHeader
    {
        [FieldOffset(0)]
        internal ushort BufferSize;
        [FieldOffset(4)]
        internal byte Type;
        [FieldOffset(5)]
        internal byte Level;
        [FieldOffset(6)]
        internal short Version;
        [FieldOffset(8)]
        internal ulong HistoricalContext;
        [FieldOffset(16)]
        internal Int64 TimeStamp;
        [FieldOffset(24)]
        internal System.Guid Guid;
        [FieldOffset(40)]
        internal uint ClientContext;
        [FieldOffset(44)]
        internal uint Flags;
    }

    // Trace event data structure used by this provider.
    [StructLayout(LayoutKind.Explicit, Size = 80)]
    internal struct TraceEvent
    {
        [FieldOffset(0)]
        internal EventTraceHeader Header;
        [FieldOffset(48)]
        internal MofField Mof1;
        [FieldOffset(64)]
        internal MofField Mof2;
    }

    /// <summary>
    /// The MOF_FIELD structure is used to append event data to the EVENT_TRACE_HEADER structure. 
    /// </summary>
    [StructLayout(LayoutKind.Explicit, Size = 16)]
    internal struct MofField
    {
        [FieldOffset(0)]
        internal IntPtr Data;
        [FieldOffset(8)]
        internal uint Length;
        [FieldOffset(12)]
        internal uint Type;
    }

    /// <summary>
    /// The TRACE_GUID_REGISTRATION structure is used to register event trace classes.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    internal struct TraceGuidRegistration
    {
        internal unsafe Guid* Guid;
        internal unsafe void* RegHandle;
    }

    static class EtwNativeMethods
    {
        /// <summary>
        /// The TraceEvent function sends an event to an event tracing session.
        /// </summary>
        /// <param name="_traceHandle">Handle to the event tracing session that records the event</param>
        /// <param name="header">Pointer to an EVENT_TRACE_HEADER structure</param>
        /// <returns>
        /// If the function succeeds, returns ERROR_SUCCESS. If the function fails, returns one of the system error codes.
        /// </returns>
        [DllImport("advapi32", ExactSpelling = true, EntryPoint = "TraceEvent", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
        internal static extern unsafe uint TraceEvent(ulong _traceHandle, char* header);

        /// <summary>
        /// The RegisterTraceGuids function registers an event trace provider. It also specifies the function the provider uses 
        /// to enable and disable tracing.
        /// </summary>
        /// <param name="cbFunc">ControlCallback function that receives notification when the provider is enabled or disabled</param>
        /// <param name="context">An optional provider-defined context that ETW passes to the function specified by cbFunc</param>
        /// <param name="controlGuid">GUID of the registering provider</param>
        /// <param name="guidCount">Number of elements in the TraceGuidReg array</param>
        /// <param name="guidReg">Array of TRACE_GUID_REGISTRATION structures</param>
        /// <param name="mofImagePath">This parameter is not supported, set to NULL</param>
        /// <param name="mofResourceName">This parameter is not supported, set to NULL</param>
        /// <param name="regHandle">The provider's registration handle</param>
        /// <returns>
        /// If the function succeeds, returns ERROR_SUCCESS. If the function fails, returns one of the system error codes.
        /// </returns>
        [DllImport("advapi32", ExactSpelling = true, EntryPoint = "RegisterTraceGuidsW", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
        internal static extern unsafe uint RegisterTraceGuids([In] EtwTraceCallback cbFunc, [In] void* context, [In] ref System.Guid controlGuid, [In] uint guidCount, [In] ref TraceGuidRegistration guidReg, [In] string mofImagePath, [In] string mofResourceName, [Out] out ulong regHandle);

        /// <summary>
        /// The UnregisterTraceGuids function unregisters an event trace provider. 
        /// </summary>
        /// <param name="regHandle">Handle to the event trace provider, obtained from an earlier call to the RegisterTraceGuids</param>
        /// <returns>
        /// If the function succeeds, returns ERROR_SUCCESS. If the function fails, returns one of the system error codes.
        /// </returns>
        [DllImport("advapi32", ExactSpelling = true, EntryPoint = "UnregisterTraceGuids", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
        internal static extern int UnregisterTraceGuids(ulong regHandle);
    }
}
